/*
 * The pathological world file format; based on RRIFF
 * 
 * Everything is case-sensitive
 * Each line is of the form: [name = class [type] [args]][# Comment]
 * A backslash character preceding a newline indicates line continuation
 *
 * name is a unique C-compatible identifier for the object
 * class is one of: V, O, G, M, C
 * type is class-specific
 * args are class/type-specific
 *
 * V (view) has no types; args are: (locx,locy,locz) (vpnx,vpny,vpnz)
 *  (upx,upy,upz) hfov vfov
 *   - Fields of view are in degrees
 * O (object) has no types; args are: gobject mobject
 * C (color) has rgb, spectrum as types; args are (r,g,b), (lambda,power,...)
 * G (geometry) has primitives as types; args are primitive-specific
 * M (material) has BSDFs as types; args are BSDF-specific
 *
 * Primitive args:
 *  sphere (centerx,centery,centerz) radius
 *  triangle (ax,ay,az) (bx,by,bz) (cx,cy,cz)
 *   - Viewed from the front, vertices are, clockwise, a, b, c
 *
 * BSDF args:
 *  lambertian reflect_color [emit_color]
 *  mirror reflect_color [emit_color]
 *  dielectric ior_color absorb_color [emit_color]
 *   - absorb_color is A in e^-lA for Beer's law
 *
 * TODO: among other things, use lex for this
 */

#include "pathological.h"


#define NAME_LENGTH 100


typedef enum {
	ERR_NONE,
	ERR_NAME,
	ERR_CLASS,
	ERR_TYPE,
	ERR_ARG,
	ERR_UNKNOWN
} pwld_error_t;

typedef enum {
	PWLD_SPHERE,
	PWLD_TRIANGLE
} pwld_primitive_type_t;

typedef enum {
	PWLD_LAMBERT,
	PWLD_MIRROR,
	PWLD_DIELECT
} pwld_material_type_t;


struct {
	char *name;
	color_t color;
} *colorlist = NULL;
int colorlistentries = 0;

struct {
	char *name;
	pwld_primitive_type_t type;
	union {
		struct {vector_t c; double r;} sphere;
		struct {vector_t a, b, c;} triangle;
	} data;
} *geolist = NULL;
int geolistentries = 0;

struct {
	char *name;
	pwld_material_type_t type;
	union {
		struct {color_t *reflect, *emit;} lambert;
		struct {color_t *reflect, *emit;} mirror;
		struct {color_t *ior, *absorb, *emit;} dielect;
	} data;
} *matlist = NULL;
int matlistentries = 0;


void pwld_die(pwld_error_t error, int line)
{
	switch(error)
	{
		case ERR_NAME:
			printf("\npathological: error: missing/invalid name on line %i\n",
				line);
			break;
		case ERR_CLASS:
			printf("\npathological: error: missing/invalid class on line %i\n",
				line);
			break;
		case ERR_TYPE:
			printf("\npathological: error: missing/invalid type on line %i\n",
				line);
			break;
		case ERR_ARG:
			printf("\npathological: error: missing/invalid arg on line %i\n",
				line);
			break;
		case ERR_UNKNOWN:
		default: // Uh, sure...
			printf("\npathological: error: unknown error on line %i\n",
				line);
			break;
	}

	exit(1);
}

void pwld_export_object(int geoi, int mati)
{
	triangle_t *t;
	object_t *o = NULL;

	switch(geolist[geoi].type)
	{
		case PWLD_SPHERE:
			o = calloc(sizeof(sphere_t),1);
			o->type = OBJ_SPHERE;
			((sphere_t *)o)->c = geolist[geoi].data.sphere.c;
			((sphere_t *)o)->r = geolist[geoi].data.sphere.r;
			o->intersect = sphere_intersect;
			o->surface_point = sphere_surface_point;
			o->aabb = sphere_aabb;
			break;
		case PWLD_TRIANGLE:
			t = (triangle_t *)(o = calloc(sizeof(triangle_t),1));
			o->type = OBJ_TRIANGLE;
			t->v = geolist[geoi].data.triangle.a;
			vec_sub(&geolist[geoi].data.triangle.b,&t->v,&t->s1);
			vec_sub(&geolist[geoi].data.triangle.c,&t->v,&t->s2);
			t->n.x = t->s1.y*t->s2.z - t->s1.z*t->s2.y,
			t->n.y = t->s1.z*t->s2.x - t->s1.x*t->s2.z,
			t->n.z = t->s1.x*t->s2.y - t->s1.y*t->s2.x;
			normalize_vec(&t->n);
			o->intersect = triangle_intersect;
			o->surface_point = triangle_surface_point;
			o->aabb = triangle_aabb;
			break;
		default: pwld_die(ERR_UNKNOWN,-1);
	}

	switch(matlist[mati].type)
	{
		case PWLD_LAMBERT:
			if(matlist[mati].data.lambert.emit)
				o->mat.flags |= MAT_EMITS,
				o->mat.emission = *matlist[mati].data.lambert.emit;
			else rgb_to_color(&o->mat.emission,0,0,0);
			o->mat.diffuse = *matlist[mati].data.lambert.reflect;
			o->mat.ior = ambientior;
			o->mat.bsdf = lambert_bsdf;
			o->mat.sample_bsdf = lambert_sample_bsdf;
			break;
		case PWLD_MIRROR:
			if(matlist[mati].data.mirror.emit)
				o->mat.flags |= MAT_EMITS,
				o->mat.emission = *matlist[mati].data.mirror.emit;
			else rgb_to_color(&o->mat.emission,0,0,0);
			o->mat.specular = *matlist[mati].data.mirror.reflect;
			o->mat.ior = ambientior;
			o->mat.bsdf = mirror_bsdf;
			o->mat.sample_bsdf = mirror_sample_bsdf;
			break;
		case PWLD_DIELECT:
			if(matlist[mati].data.dielect.emit)
				o->mat.flags |= MAT_EMITS,
				o->mat.emission = *matlist[mati].data.dielect.emit;
			else rgb_to_color(&o->mat.emission,0,0,0);
			o->mat.transmit = *matlist[mati].data.dielect.absorb;
			o->mat.ior = *matlist[mati].data.dielect.ior;
			o->mat.bsdf = fresnel_bsdf;
			o->mat.sample_bsdf = fresnel_sample_bsdf;
			break;
		default: pwld_die(ERR_UNKNOWN,-1);
	}

	add_object(o);
}

// Handles line continuation and discards comments
void pwld_read_line(FILE *f, char **s)
{
	int c, discard = false, ssize = 100, i = 0;

	assert(*s = realloc(*s,ssize));

	while((c = fgetc(f)) != EOF && c != '\n')
	{
		if(c == '#') discard = true;
		if(c == '\\' && (c == fgetc(f)) != '\n') (*s)[i++] = '\\';
		if(!discard)
		{
			if(i > ssize - 2) *s = realloc(*s,ssize *= 2);
			(*s)[i++] = c;
		}
	}
	(*s)[i++] = '\0';

	assert(*s = realloc(*s,i));
}

pwld_error_t pwld_add_view(char *name, char *typeargs)
{
	camera_t cam;
	vector_t vpn, up, cp;
	double hfov, vfov, s;

	if(sscanf(typeargs," (%lf ,%lf ,%lf ) (%lf ,%lf ,%lf ) (%lf ,%lf ,%lf ) "
		"%lf %lf",&cam.x,&cam.y,&cam.z,&vpn.x,&vpn.y,&vpn.z,&up.x,&up.y,&up.z,
		&hfov,&vfov) != 11 || hfov < 0 || hfov >= 180 || vfov < 0
		|| vfov >= 180) return ERR_ARG;

	normalize_vec(&vpn);

	s = dot(&up,&vpn);
	up.x -= s*vpn.x,
	up.y -= s*vpn.y,
	up.z -= s*vpn.z;
	normalize_vec(&up);
	vec_scale(&up,tan(vfov/2*(PI/180)));

	cp.x = up.y*vpn.z - up.z*vpn.y,
	cp.y = up.z*vpn.x - up.x*vpn.z,
	cp.z = up.x*vpn.y - up.y*vpn.x;
	normalize_vec(&cp);
	vec_scale(&cp,tan(hfov/2*(PI/180)));

	cam.orgin.x = -cp.x + up.x + vpn.x,
	cam.orgin.y = -cp.y + up.y + vpn.y,
	cam.orgin.z = -cp.z + up.z + vpn.z;

	cam.xstep = cp, vec_scale(&cam.xstep,2.0/width);
	cam.ystep = up, vec_scale(&cam.ystep,-2.0/height);

	camera = cam; // TODO: multi-view support

	return ERR_NONE;
}

pwld_error_t pwld_add_object(char *name, char *typeargs)
{
	char *arg;
	int i, gi, mi;
   
	arg = strtok(typeargs," \t");

	while(strlen(arg) == 0) arg = strtok(NULL," \t");
	for(i = 0, gi = -1; i < geolistentries; i++)
		if(strcmp(arg,geolist[i].name) == 0)
			gi = i;
	if(gi < 0) return ERR_ARG;

	do arg = strtok(NULL," \t"); while(strlen(arg) == 0);
	for(i = 0, mi = -1; i < matlistentries; i++)
		if(strcmp(arg,matlist[i].name) == 0)
			mi = i;
	if(mi < 0) return ERR_ARG;

	pwld_export_object(gi,mi);

	return ERR_NONE;
}

pwld_error_t pwld_add_color(char *name, char *typeargs)
{
	char type[10];
	int i = colorlistentries, j;

	colorlist = realloc(colorlist,++colorlistentries*sizeof(*colorlist));

	colorlist[i].name = strcpy(malloc(strlen(name) + 1),name);

	if(sscanf(typeargs," %9s",type) != 1) return ERR_TYPE;
	if(strcmp(type,"rgb") == 0)
	{
		float r, g, b;
		if(sscanf(typeargs," %*s (%f ,%f ,%f )",&r,&g,&b) != 3) return ERR_ARG;
		rgb_to_color(&colorlist[i].color,r,g,b);
	}
	else if(strcmp(type,"spectrum") == 0)
	{
		char *tok;
		spectrum_t s;
		s.numsamples = 0, s.samples = NULL;
		if(!strtok(typeargs,"(")) return ERR_ARG;
		while((tok = strtok(NULL,",")))
		{
			j = s.numsamples;
			s.samples
				= realloc(s.samples,++s.numsamples*sizeof(spectrum_sample_t));
			s.samples[j].w = atof(tok);
			if(!(tok = strtok(NULL,","))) return ERR_ARG;
			s.samples[j].i = atof(tok);
		}
		resample_spectrum(&colorlist[i].color,&s);
	}
	else return ERR_TYPE;

	return ERR_NONE;
}

pwld_error_t pwld_add_geometry(char *name, char *typeargs)
{
	char type[10];
	int i = geolistentries;
	
	geolist = realloc(geolist,++geolistentries*sizeof(*geolist));

	geolist[i].name = strcpy(malloc(strlen(name) + 1),name);

	if(sscanf(typeargs," %9s",type) != 1) return ERR_TYPE;
	if(strcmp(type,"sphere") == 0)
	{
		geolist[i].type = PWLD_SPHERE;
		if(sscanf(typeargs," %*s (%lf ,%lf ,%lf ) %lf",
			&geolist[i].data.sphere.c.x,&geolist[i].data.sphere.c.y,
			&geolist[i].data.sphere.c.z,&geolist[i].data.sphere.r) != 4)
			return ERR_ARG;
	}
	else if(strcmp(type,"triangle") == 0)
	{
		geolist[i].type = PWLD_TRIANGLE;
		if(sscanf(typeargs," %*s (%lf ,%lf ,%lf ) (%lf ,%lf ,%lf ) (%lf ,%lf ,"
			"%lf )",&geolist[i].data.triangle.a.x,
			&geolist[i].data.triangle.a.y,&geolist[i].data.triangle.a.z,
			&geolist[i].data.triangle.b.x,&geolist[i].data.triangle.b.y,
			&geolist[i].data.triangle.b.z,&geolist[i].data.triangle.c.x,
			&geolist[i].data.triangle.c.y,&geolist[i].data.triangle.c.z) != 9)
			return ERR_ARG;
	}
	else return ERR_TYPE;

	return ERR_NONE;
}

pwld_error_t pwld_add_material(char *name, char *typeargs)
{
	int argi;
	char type[12], *tok;
	int i = matlistentries, j, n;
	
	matlist = realloc(matlist,++matlistentries*sizeof(*matlist));
	memset(matlist + i,0,sizeof(*matlist));

	matlist[i].name = strcpy(malloc(strlen(name) + 1),name);

	if(sscanf(typeargs," %11s%n",type,&n) != 1) return ERR_TYPE;

	for(tok = strtok(typeargs + n," \t"), argi = 0; tok;
		tok = strtok(NULL," \t"))
	{
		if(strlen(tok) == 0) continue;

		for(j = 0; j < colorlistentries && strcmp(tok,colorlist[j].name); j++);
		if(strcmp(tok,colorlist[j].name)) return ERR_ARG;

		if(strcmp(type,"lambertian") == 0)
			switch(argi)
			{
				case 0: matlist[i].data.lambert.reflect = &colorlist[j].color;
					matlist[i].type = PWLD_LAMBERT;
					break;
				case 1: matlist[i].data.lambert.emit = &colorlist[j].color;
					break;
				default: return ERR_ARG;
			}
		else if(strcmp(type,"mirror") == 0)
			switch(argi)
			{
				case 0: matlist[i].data.mirror.reflect = &colorlist[j].color;
					matlist[i].type = PWLD_MIRROR;
					break;
				case 1: matlist[i].data.mirror.emit = &colorlist[j].color;
					break;
				default: return ERR_ARG;
			}
		else if(strcmp(type,"dielectric") == 0)
			switch(argi)
			{
				case 0: matlist[i].data.dielect.ior = &colorlist[j].color;
					matlist[i].type = PWLD_DIELECT;
					break;
				case 1: matlist[i].data.dielect.absorb = &colorlist[j].color;
					break;
				case 2: matlist[i].data.dielect.emit = &colorlist[j].color;
					break;
				default: return ERR_ARG;
			}
		else return ERR_TYPE;

		argi++;
	}

	return ERR_NONE;
}

void pwld_cleanup()
{
	int i;

	for(i = 0; i < geolistentries; i++)
		free(geolist[i].name);
	free(geolist);

	for(i = 0; i < colorlistentries; i++)
		free(colorlist[i].name);
	free(colorlist);
}

void pwld_import(FILE *f)
{
	int linenum = 0, i;
	pwld_error_t err = ERR_NONE;
	char *name = malloc(NAME_LENGTH), class, *line = NULL;

	while(!feof(f))
	{
		// Parse one logical line

		pwld_read_line(f,&line);
		linenum++;

		switch(sscanf(line," %1[a-zA-Z_$]%[a-zA-Z_$0-9] = %1[VvOoCcGgMm]%n",
			name,name + 1,&class,&i))
		{
			case EOF: continue;
			case 0: continue;
			case 1: pwld_die(ERR_NAME,linenum); break;
			case 2: pwld_die(ERR_CLASS,linenum); break;
			case 3: break;
			default: break;
		}

		switch(class > 'Z' ? class - 'a' + 'A' : class)
		{
			case 'V': err = pwld_add_view(name,line + i); break;
			case 'O': err = pwld_add_object(name,line + i); break;
			case 'C': err = pwld_add_color(name,line + i); break;
			case 'G': err = pwld_add_geometry(name,line + i); break;
			case 'M': err = pwld_add_material(name,line + i); break;
			default: err = ERR_CLASS;
		}

		if(err != ERR_NONE) pwld_die(err,linenum);
	}

	pwld_cleanup();
	free(line);
	free(name);
}

